/* Dancing Links DLX精确覆盖问题
* 1.DS 十字链表


* 2.题型
    1.精确覆盖问题
    2.配合覆盖问题(配合IDA*)

* 3.优化
    找到一个含 1 最少的列，然后再该列中枚举选择哪一行，
    如果选择某一行，则其他行应该删除，因为精确覆盖每一列有且只有一个 1，
    另外对于选择的行，其中出现 1 的列也应该删除，
    另外需要注意的一点：删除列时对于真正节点只需要在上下方向删除，
    因为后面对于某含最少个数的 1 的列，通过上下指针枚举行，
    而那些要删除的行我们已经在上下方向删除了不会被枚举到，恢复时反向也好恢复
*/

#include <cstdio>
using namespace std;
#define DEBUG
inline int read()
{
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48,c=getchar();
	return x;
}
inline void write(int x)
{
	if (x>=10) write(x/10);
	putchar(x%10+48);
}//快读快输

const int N=6e3+10, INF=0x3f3f3f3f;

int n,m;
int l[N], r[N], u[N], d[N], s[N], row[N], col[N], idx; 
//左右上下指针            每列1的数量 每个节点所在行，列 节点编号
int ans[N], top; //最终选择行 ans指针

void init() //初始化循环链表指针
{
    for(int i=0; i<=m; i++)
    {
        l[i]=i-1, r[i]=i+1;
        u[i]=d[i]=i; //只有一行，上下指针指向自己
    }
    l[0]=m, r[m]=0; //左右边界
    idx=m+1;
}

//添加节点(x, y) 列头(->idx(x,y)->...
void add(int &hh, int &tt, int x, int y) //行的头尾节点索引 新节点的位置 y是列号，也是这个列链表的头节点
{
    row[idx]=x, col[idx]=y, s[y]++; //记录当前节点位置(设置为横向，纵向的列头) 更新
    u[idx]=y, d[idx]=d[y], u[d[y]]=idx, d[y]=idx; //插入列y纵向链表 y->idx->...
    //新节点的上个节点是队头y 新节点的下个节点是原队头的下个节点 原下个节点的上个节点是新节点
    r[hh]=l[tt]=idx, r[idx]=tt, l[idx]=hh, tt=idx++; //横向链表队尾
} 

void remove(int pos) //删除pos列
{
    r[l[pos]]=r[pos], l[r[pos]]=l[pos]; //列头节点从横向链表删除
    for(int i=d[pos]; i!=pos; i=d[i]) //pos列是1的所有行删除
        for(int j=r[i]; j!=i; j=r[j]) //i所在行更新
        {
            s[col[j]]--;
            u[d[j]]=u[j], d[u[j]]=d[j]; //纵向删除
        }
}

void resume(int pos) //恢复pos列 与remove逆操作，访问顺序相反
{
    for(int i=u[pos]; i!=pos; i=u[i])
        for(int j=l[i]; j!=i; j=l[j])
        {
            u[d[j]]=j, d[u[j]]=j;
            s[col[j]]++;
        }
    r[l[pos]]=pos, l[r[pos]]=pos;
}

bool dfs()
{
    if(!r[0]) return true; //所有行已删除
    //找到1的个数最少的列
    int pos=r[0];
    for(int i=r[0]; i; i=r[i])
        if(s[i]<s[pos])
            pos=i;
     
        

    remove(pos); //删除这一列
    for(int i=d[pos]; i!=pos; i=d[i]) //枚举当前选择行
    {
        ans[++top]=row[i];
        for(int j=r[i]; j!=i; j=r[j]) remove(col[j]); //当前行中的其他1所在列删除
        if(dfs()) return true;
        //无解，恢复现场
        for(int j=l[i]; j!=i; j=l[j]) resume(col[j]); //恢复当前行
        top--;
    }
    resume(pos);
    return false;
}

signed solve()
{
    n=read(), m=read();
    init();
    for(int i=1; i<=n; i++) //插入第i行
    {
        int hh=idx, tt=idx;
        for(int j=1; j<=m; j++)
        {
            int x=read();
            if(x) add(hh, tt, i, j);
        }
    }
    if(dfs())
        for(int i=1; i<=top;i++) write(ans[i]), putchar(' ');
    else
        puts("No Solution!");
    puts("");
    return 0;
}


signed main()
{
    #ifdef DEBUG
        freopen("./in.txt","r",stdin);
        freopen("./out.txt","w",stdout);
    #endif

    
    int T=1; //T=read();
    while(T--)
    {
        solve();
    }
    return 0;
}